package com.ivanceras.server.util;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.ivanceras.server.db.DBPool;
import com.ivanceras.server.db.DB_PostgreSQL;
import com.ivanceras.shared.db.ModelDef;
import com.ivanceras.shared.db.IDatabase;
import com.ivanceras.shared.db.IModelDef;
import com.ivanceras.shared.exception.DatabaseException;
import com.ivanceras.shared.util.SpecialCase;

/**
 * Creates a dao definition based on the database tables
 * @author jcesar
 *
 */
public class DAOGenerator{
	static String dbPackage = Configuration.dbPackage;
	static String sharedDbPackage = Configuration.sharedDbPackage;
	static String daodirectory = Configuration.daodirectory;
	static String daopackageName = Configuration.daopackageName;
	static String metaDataDirectory = Configuration.metaDataDirectory;
	static String metaDataPackageName = Configuration.metaDataPackageName;
	static String bodirectory = Configuration.bodirectory;
	static String bopackageName = Configuration.bopackageName;
	static String mapperdirectory = Configuration.mapperdirectory;
	static String mapperpackageName = Configuration.mapperpackageName;

	static boolean generateStubs(IDatabase db, String schema, String tablePattern) throws DatabaseException {
		String[] tableNames = getTableNames(db, schema, tablePattern);
		for(String table : tableNames){
			try {
				IModelDef modelDef = db.getModelMetaData(table);
				System.out.println(modelDef.toSummaryString());
				generateDAOClass(daodirectory, daopackageName, modelDef);
				generateBusinessObjectClass(bodirectory, bopackageName, modelDef);
				generateMapperClass(mapperdirectory, mapperpackageName, modelDef);
			} catch (DatabaseException e) {
				e.printStackTrace();
			}
		}

		return true;
	}

	public static String[] getTableNames(IDatabase db, String schema, String tablePattern) throws DatabaseException{
		List<String> tableNames = new ArrayList<String>();
		try {
			String sql = "SELECT tablename from pg_tables where schemaname = ? and tablename like ? order by tablename";
			Object[] parameters = new Object[2];
			parameters[0] = schema;
			parameters[1] = tablePattern;
			ResultSet rs = ((DB_PostgreSQL) db).executeSelectSQL(sql, parameters);
			while(rs.next()){
				String table = rs.getString("tablename");
				tableNames.add(table);
				System.err.println(table);

			}
		} catch (SQLException e) {
			e.printStackTrace();
			throw new DatabaseException();
		}
		return tableNames.toArray(new String[tableNames.size()]);

	}

	private static String[] getAllTableNames(IDatabase db, String schema) throws DatabaseException{
		return getTableNames(db, schema, "%");

	}
	private static boolean generateAllModelMetaData(IDatabase db, String schema) throws DatabaseException{
		String[] tableNames = getAllTableNames(db, schema);
		List<IModelDef> modeldefList = new ArrayList<IModelDef>();
		for(String table : tableNames){
			try {
				ModelDef modelDef = (ModelDef) db.getModelMetaData(table);
				modelDef.setNamespace(schema);
				modeldefList.add(modelDef);
				System.out.println(modelDef.toSummaryString());
			} catch (DatabaseException e) {
				e.printStackTrace();
			}
		}
		generateModelMetaData(modeldefList, schema);
		return true;
	}

	private static boolean generateModelMetaData(List<IModelDef> modeldefList, String schema) {
		StringBuffer buff = new StringBuffer();
		String metaDataClassName = CStringUtils.capitalize(schema)+"ModelMetaData";
		buff.append("package "+metaDataPackageName+";");
		buff.append("\n");
		buff.append("\nimport java.util.HashMap;");
//		buff.append("\nimport java.util.Map;");
//		buff.append("\nimport com.ivanceras.shared.exception.DatabaseException;");
		buff.append("\n");
		buff.append("\nimport "+sharedDbPackage+".IModelDef;");
		buff.append("\nimport "+sharedDbPackage+".ModelDef;");
		buff.append("\n");
		buff.append("\npublic class "+metaDataClassName+"{");
		buff.append("\n");
		buff.append("\n");
		buff.append("\n\tpublic HashMap<String, IModelDef> modelDefList = new HashMap<String, IModelDef>();");
		buff.append("\n");
		for(int i = 0; i < modeldefList.size(); i++){
			IModelDef modeldef = modeldefList.get(i);
			String className = CStringUtils.capitalize(modeldef.getModelName());
			buff.append("\n");
			buff.append("\n\tvoid init"+i+"(){");
			buff.append("\n");
			buff.append("\n\t\tString   namespace"+i+"                               = \""+modeldef.getNamespace()+"\";");
			buff.append("\n\t\tString   modelName"+i+"                               = \""+className+"\";");
			buff.append("\n\t\tString[] attributes"+i+"                              = "+getStringListRepresentation(modeldef.getAttributes())+";");
			buff.append("\n\t\tString   generatedAttribute"+i+"                      = "+(modeldef.getGeneratedAttribute()!=null?("\""+modeldef.getGeneratedAttribute()+"\""):null)+";");
			buff.append("\n\t\tString[] primaryAttributes"+i+"                       = "+getStringListRepresentation(modeldef.getPrimaryAttributes())+";");
			buff.append("\n\t\tString[] uniqueAttributes"+i+"                        = "+getStringListRepresentation(modeldef.getUniqueAttributes())+";");
			buff.append("\n\t\tString[] dataTypes"+i+"                               = "+getStringListRepresentation(modeldef.getDataTypes())+";");
			buff.append("\n\t\tString[] hasOne"+i+"                                  = "+getStringListRepresentation(modeldef.getHasOne())+";");
			buff.append("\n\t\tString[] hasOneForeignColumn"+i+"                     = "+getStringListRepresentation(modeldef.getHasOneForeignColumn())+";");
			buff.append("\n\t\tString[] hasMany"+i+"                                 = "+getStringListRepresentation(modeldef.getHasMany())+";");
			buff.append("\n\t\tString[] hasManyForeignColumn"+i+"                    = "+getStringListRepresentation(modeldef.getHasManyForeignColumn())+";");
			buff.append("\n\t\tString[] hasManyMany"+i+"                             = "+getStringListRepresentation(modeldef.getHasManyMany())+";");
			buff.append("\n\t\tString[] hasManyManyInvolvedModel"+i+"                = "+getStringListRepresentation(modeldef.getHasManyManyInvolvedModel())+";");
			buff.append("\n\t\tString[] hasManyManyInvolvedModelForeignColumn"+i+"   = "+getStringListRepresentation(modeldef.getHasManyManyInvolvedModelForeignColumn())+";");
			buff.append("\n\t\tboolean  caseSensitive"+i+"                           = "+modeldef.isCaseSensitive()+";");
			buff.append("\n\t\t");

			buff.append("\n\t\tIModelDef modelDef"+i+" = new ModelDef(namespace"+i+", modelName"+i+", attributes"+i+",");
			buff.append("\n\t\t\t\t\tgeneratedAttribute"+i+", dataTypes"+i+", ");
			buff.append("\n\t\t\t\t\tprimaryAttributes"+i+", uniqueAttributes"+i+", hasOne"+i+",");
			buff.append("\n\t\t\t\t\thasOneForeignColumn"+i+", hasMany"+i+", hasManyForeignColumn"+i+", ");
			buff.append("\n\t\t\t\t\thasManyMany"+i+", hasManyManyInvolvedModel"+i+", hasManyManyInvolvedModelForeignColumn"+i+", caseSensitive"+i+");");
			buff.append("\n\t\t\t\t");
			buff.append("\n\t\tmodelDefList.put(\""+className+"\",modelDef"+i+");");
			buff.append("\n");
			buff.append("\n");
			buff.append("\n\t}");
		}
		buff.append("\n");
		buff.append("\n");
		buff.append("\n\tpublic "+metaDataClassName+"(){");
		for(int i = 0; i < modeldefList.size(); i++){
			buff.append("\n\t\tinit"+i+"();");
		}
		buff.append("\n\t}");
		buff.append("\n}");

		FileUtil.writeToFile(buff.toString(), metaDataDirectory, metaDataClassName+".java");
		return true;
	}

	static String getStringListRepresentation(String[] list){
		if(list == null){
			return null;
		}
		StringBuffer buff = new StringBuffer();
		buff.append("{");
		boolean doComma = false;
		for(String l : list){
			if(doComma){buff.append(", ");}else{doComma=true;}
			buff.append("\""+l+"\"");
		}
		buff.append("}");
		return buff.toString();
	}

	static boolean generateDAOClass(String directory, String packageName, IModelDef modeldef){
		StringBuffer buff = new StringBuffer();
		String className = CStringUtils.capitalize(modeldef.getModelName());
		buff.append("package "+packageName+";");
		buff.append("\n");
		buff.append("\n/***");
		buff.append("\n* This is automatically generated by com.ivanceras.util.DAOGenerator, based on the database table schema");
		buff.append("\n* ");
		buff.append("\n* ");
		buff.append("\n*/");
		buff.append("\n");
		buff.append("\nimport com.ivanceras.shared.db.DAO;");
		buff.append("\npublic class DAO_"+className+" extends DAO{");
		buff.append("\n/**");
		buff.append("\n *"); 
		buff.append("\n */");

		String[] attributes = modeldef.getAttributes();
		String[] dataTypes = modeldef.getDataTypes();
		String[] hasOne = modeldef.getHasOne();


		String[] distinctHasOne = getDistinctString(hasOne);

		String[] hasMany = modeldef.getHasMany();

		buff.append("\n");
		buff.append("\n");
		buff.append("\n\tpublic DAO_"+className+"(){");
		buff.append("\n\t\tsuper(\""+className+"\");");
		buff.append("\n\t}");
		buff.append("\n");
		for(int i = 0; i < attributes.length; i++){
			String att = attributes[i];
			att = SpecialCase.getEquiv(att);
			String datatype = dataTypes[i];
			buff.append("\n\tpublic "+datatype+" get"+CStringUtils.capitalize(att)+"(){");
			buff.append("\n\t\treturn ("+datatype+")get_Value(\""+att+"\");");
			buff.append("\n\t}");
			buff.append("\n");
			buff.append("\n\tpublic void set"+CStringUtils.capitalize(att)+"("+datatype+" "+att+"){");
			buff.append("\n\t\tset_Value(\""+att+"\", "+att+");");
			buff.append("\n\t}");
			buff.append("\n");
		}

		for(int i = 0; i < distinctHasOne.length; i++){
			buff.append("\n");
			buff.append("\n\tpublic void set"+CStringUtils.capitalize(distinctHasOne[i])+"(DAO_"+CStringUtils.capitalize(distinctHasOne[i])+" "+CStringUtils.toVariableName(distinctHasOne[i])+"){");
			buff.append("\n\t\tset_Value(\""+CStringUtils.toVariableName(distinctHasOne[i])+"\", "+CStringUtils.toVariableName(distinctHasOne[i])+");");
			buff.append("\n\t}");
			buff.append("\n");
			buff.append("\n\tpublic DAO_"+CStringUtils.capitalize(distinctHasOne[i])+" get"+CStringUtils.capitalize(distinctHasOne[i])+"(){");
			buff.append("\n\t\treturn (DAO_"+CStringUtils.capitalize(distinctHasOne[i])+")get_Value(\""+CStringUtils.toVariableName(distinctHasOne[i])+"\");");
			buff.append("\n\t}");
		}


		for(int i = 0; i < hasMany.length; i++){
			buff.append("\n");
			buff.append("\n\tpublic void set"+CStringUtils.capitalize(hasMany[i])+"List(DAO_"+CStringUtils.capitalize(hasMany[i])+"[] "+CStringUtils.toVariableName(hasMany[i])+"List){");
			buff.append("\n\t\tset_Value(\""+CStringUtils.toVariableName(hasMany[i])+"List\", "+CStringUtils.toVariableName(hasMany[i])+"List);");
			buff.append("\n\t}");
			buff.append("\n");
			buff.append("\n\tpublic DAO_"+CStringUtils.capitalize(hasMany[i])+"[] get"+CStringUtils.capitalize(hasMany[i])+"List(){");
			buff.append("\n\t\treturn (DAO_"+CStringUtils.capitalize(hasMany[i])+"[])get_Value(\""+CStringUtils.toVariableName(hasMany[i])+"List\");");
			buff.append("\n\t}");
		}
		buff.append("\n");
		buff.append("\n\tpublic static DAO_"+className+" cast(DAO dao){");
		buff.append("\n\t\tif(dao == null){");
		buff.append("\n\t\t\treturn null;");
		buff.append("\n\t\t}");
		buff.append("\n\t\tDAO_"+className+" "+CStringUtils.toVariableName(className)+" = new DAO_"+className+"();");
		buff.append("\n\t\t"+CStringUtils.toVariableName(className)+".setProperties(dao.getProperties());");
		buff.append("\n\t\treturn "+CStringUtils.toVariableName(className)+";");
		buff.append("\n\t}");
		buff.append("\n\t");
		buff.append("\n\tpublic static DAO_"+className+"[] cast (DAO[] daoList){");
		buff.append("\n\t\tif(daoList == null){");
		buff.append("\n\t\t\treturn null;");
		buff.append("\n\t\t}");
		buff.append("\n\t\tDAO_"+className+"[] "+CStringUtils.toVariableName(className)+"List = new DAO_"+className+"[daoList.length];");
		buff.append("\n\t\tfor(int i = 0; i < daoList.length; i++){");
		buff.append("\n\t\t\t"+CStringUtils.toVariableName(className)+"List[i] = cast(daoList[i]);");
		buff.append("\n\t\t}");
		buff.append("\n\t\treturn "+CStringUtils.toVariableName(className)+"List;");
		buff.append("\n\t}");
		buff.append("\n\t");
		buff.append("\n}");
		FileUtil.writeToFile(buff.toString(), directory, "DAO_"+className+".java");
		return true;
	}


	static boolean generateBusinessObjectClass(String directory, String packageName, IModelDef modeldef){
		StringBuffer buff = new StringBuffer();
		String className = CStringUtils.capitalize(modeldef.getModelName());
		buff.append("package "+packageName+";");
		buff.append("\n");
		buff.append("\n/***");
		buff.append("\n* This is automatically generated by com.ivanceras.util.DAOGenerator, based on the database table schema");
		buff.append("\n* ");
		buff.append("\n* ");
		buff.append("\n*/");
		buff.append("\nimport java.io.Serializable;");
		buff.append("\n");
		buff.append("\npublic class "+className+" implements Serializable{");
		buff.append("\n/**");
		buff.append("\n *"); 
		buff.append("\n */");

		String[] attributes = modeldef.getAttributes();
		String[] dataTypes = modeldef.getDataTypes();
		String[] hasOne = modeldef.getHasOne();


		String[] distinctHasOne = getDistinctString(hasOne);

		String[] hasMany = modeldef.getHasMany();

		for(int i = 0; i < attributes.length; i++){
			String att = attributes[i];
			att = SpecialCase.getEquiv(att);
			String datatype = dataTypes[i];
			buff.append("\n\tprivate "+datatype+" "+CStringUtils.toVariableName(att)+";");
		}
		for(int i = 0; i < distinctHasOne.length; i++){
			buff.append("\n");
			buff.append("\n\tprivate "+CStringUtils.capitalize(distinctHasOne[i])+" "+CStringUtils.toVariableName(distinctHasOne[i])+";");
		}
		for(int i = 0; i < hasMany.length; i++){
			buff.append("\n");
			buff.append("\n\tprivate "+CStringUtils.capitalize(hasMany[i])+"[] "+CStringUtils.toVariableName(hasMany[i])+"List;");
		}


		buff.append("\n");
		buff.append("\n");
		buff.append("\n\tpublic "+className+"(){");
		buff.append("\n\t\t");
		buff.append("\n\t}");
		buff.append("\n");
		for(int i = 0; i < attributes.length; i++){
			String att = attributes[i];
			att = SpecialCase.getEquiv(att);
			String datatype = dataTypes[i];
			buff.append("\n\tpublic "+datatype+" get"+CStringUtils.capitalize(att)+"(){");
			buff.append("\n\t\treturn "+att+";");
			buff.append("\n\t}");
			buff.append("\n");
			buff.append("\n\tpublic void set"+CStringUtils.capitalize(att)+"("+datatype+" "+att+"){");
			buff.append("\n\t\tthis."+att+" = "+att+";");
			buff.append("\n\t}");
			buff.append("\n");
		}

		for(int i = 0; i < distinctHasOne.length; i++){
			buff.append("\n");
			buff.append("\n\tpublic void set"+CStringUtils.capitalize(distinctHasOne[i])+"("+CStringUtils.capitalize(distinctHasOne[i])+" "+CStringUtils.toVariableName(distinctHasOne[i])+"){");
			buff.append("\n\t\tthis."+CStringUtils.toVariableName(distinctHasOne[i])+" = "+CStringUtils.toVariableName(distinctHasOne[i])+";");
			buff.append("\n\t}");
			buff.append("\n");
			buff.append("\n\tpublic "+CStringUtils.capitalize(distinctHasOne[i])+" get"+CStringUtils.capitalize(distinctHasOne[i])+"(){");
			buff.append("\n\t\treturn "+CStringUtils.toVariableName(distinctHasOne[i])+";");
			buff.append("\n\t}");
		}


		for(int i = 0; i < hasMany.length; i++){
			buff.append("\n");
			buff.append("\n\tpublic void set"+CStringUtils.capitalize(hasMany[i])+"List("+CStringUtils.capitalize(hasMany[i])+"[] "+CStringUtils.toVariableName(hasMany[i])+"List){");
			buff.append("\n\t\tthis."+CStringUtils.toVariableName(hasMany[i])+"List = "+CStringUtils.toVariableName(hasMany[i])+"List;");
			buff.append("\n\t}");
			buff.append("\n");
			buff.append("\n\tpublic "+CStringUtils.capitalize(hasMany[i])+"[] get"+CStringUtils.capitalize(hasMany[i])+"List(){");
			buff.append("\n\t\treturn "+CStringUtils.toVariableName(hasMany[i])+"List;");
			buff.append("\n\t}");
		}

		buff.append("\n}");
		FileUtil.writeToFile(buff.toString(), directory, ""+className+".java");
		return true;
	}

	static boolean generateMapperClass(String directory, String packageName, IModelDef modeldef){
		StringBuffer buff = new StringBuffer();
		String className = CStringUtils.capitalize(modeldef.getModelName());
		buff.append("package "+packageName+";");
		buff.append("\n");
		buff.append("\n/***");
		buff.append("\n* This is automatically generated by com.ivanceras.util.DAOGenerator, based on the database table schema");
		buff.append("\n* ");
		buff.append("\n* ");
		buff.append("\n*/");
		buff.append("\nimport "+daopackageName+".DAO_"+className+";");
		buff.append("\nimport "+bopackageName+"."+className+";");
		buff.append("\n");
		buff.append("\npublic class "+className+"Mapper{");
		buff.append("\n/**");
		buff.append("\n *"); 
		buff.append("\n */");

		String[] attributes = modeldef.getAttributes();
		String[] hasOne = modeldef.getHasOne();


		String[] distinctHasOne = getDistinctString(hasOne);

		String[] hasMany = modeldef.getHasMany();

		/**
		 * map from DAO to Business Object
		 */
		buff.append("\n\tpublic static "+className+" map(DAO_"+className+" dao"+className+"){");
		String boObject = CStringUtils.toVariableName(className);
		buff.append("\n\t\tif(dao"+className+" == null){");
		buff.append("\n\t\t\treturn null;");
		buff.append("\n\t\t}");
		buff.append("\n\t\t"+className+" "+boObject+" = new "+className+"();"); 
		for(int i = 0; i < attributes.length; i++){
			String att = attributes[i];
			att = SpecialCase.getEquiv(att);
			buff.append("\n\t\t"+boObject+".set"+CStringUtils.capitalize(att)+"(dao"+className+".get"+CStringUtils.capitalize(att)+"());");
		}
		for(int j = 0; j < distinctHasOne.length; j++){
			buff.append("\n\t\t"+boObject+".set"+CStringUtils.capitalize(distinctHasOne[j])+"("+CStringUtils.capitalize(distinctHasOne[j])+"Mapper.map("+"dao"+className+".get"+CStringUtils.capitalize(distinctHasOne[j])+"()));");
		}
		for(int k = 0; k < hasMany.length; k++){
			buff.append("\n\t\t"+boObject+".set"+CStringUtils.capitalize(hasMany[k])+"List("+CStringUtils.capitalize(hasMany[k])+"Mapper.map(dao"+className+".get"+CStringUtils.capitalize(hasMany[k])+"List()));");
		}

		buff.append("\n\t\treturn "+boObject+";");
		buff.append("\n\t}");
		buff.append("\n");

		/**
		 * map from Business Object to DAO
		 */
		buff.append("\n\tpublic static DAO_"+className+" map("+className+" "+boObject+"){");
		buff.append("\n\t\tif("+boObject+" == null){");
		buff.append("\n\t\t\treturn null;");
		buff.append("\n\t\t}");

		buff.append("\n\t\tDAO_"+className+" dao"+className+" = new DAO_"+className+"();"); 
		for(int i = 0; i < attributes.length; i++){
			String att = attributes[i];
			att = SpecialCase.getEquiv(att);
			buff.append("\n\t\tdao"+className+".set"+CStringUtils.capitalize(att)+"("+boObject+".get"+CStringUtils.capitalize(att)+"());");
		}
		for(int j = 0; j < distinctHasOne.length; j++){
			buff.append("\n\t\tdao"+className+".set"+CStringUtils.capitalize(distinctHasOne[j])+"("+CStringUtils.capitalize(distinctHasOne[j])+"Mapper.map("+boObject+".get"+CStringUtils.capitalize(distinctHasOne[j])+"()));");
		}
		for(int k = 0; k < hasMany.length; k++){
			buff.append("\n\t\tdao"+className+".set"+CStringUtils.capitalize(hasMany[k])+"List("+CStringUtils.capitalize(hasMany[k])+"Mapper.map("+boObject+".get"+CStringUtils.capitalize(hasMany[k])+"List()));");
		}

		buff.append("\n\t\treturn dao"+className+";");
		buff.append("\n\t}");



		/**
		 * array mapping from DAO to Business Object
		 */
		buff.append("\n\tpublic static "+className+"[] map(DAO_"+className+"[] dao"+className+"List){");
		buff.append("\n\t\tif(dao"+className+"List == null){");
		buff.append("\n\t\t\treturn null;");
		buff.append("\n\t\t}");
		buff.append("\n\t\t"+className+"[] "+boObject+"List = new "+className+"[dao"+className+"List.length];");
		buff.append("\n\t\tint index = 0;");
		buff.append("\n\t\tfor(DAO_"+className+" dao"+className+" : dao"+className+"List){");
		buff.append("\n\t\t\t"+boObject+"List[index++] = map(dao"+className+");");
		buff.append("\n\t\t}");
		buff.append("\n\t\treturn "+boObject+"List;");
		buff.append("\n\t}");

		/**
		 * array mapping from Business Object to DAO
		 */
		buff.append("\n\tpublic static DAO_"+className+"[] map("+className+"[] "+boObject+"List){");
		buff.append("\n\t\tif("+boObject+"List == null){");
		buff.append("\n\t\t\treturn null;");
		buff.append("\n\t\t}");
		buff.append("\n\t\tDAO_"+className+"[] dao"+className+"List = new DAO_"+className+"["+boObject+"List.length];");
		buff.append("\n\t\tint index = 0;");
		buff.append("\n\t\tfor("+className+" "+className+" : "+boObject+"List){");
		buff.append("\n\t\t\tdao"+className+"List[index++] = map("+className+");");
		buff.append("\n\t\t}");
		buff.append("\n\t\treturn dao"+className+"List;");
		buff.append("\n\t}");

		buff.append("\n}");
		FileUtil.writeToFile(buff.toString(), directory, ""+className+"Mapper.java");

		return true;

	}

	private static String[] getDistinctString(String[] hasOne) {
		List<String> distinctString = new ArrayList<String>();
		for(String h : hasOne){
			if(distinctString.contains(h)){
				;
			}else{
				distinctString.add(h);
			}
		}
		return distinctString.toArray(new String[distinctString.size()]);
	}

	public static void main(String[] args) throws DatabaseException{
		IDatabase db = DBPool.getDBPool().connectDB();
		generateStubs(db, Configuration.dbSchema, "%");
		generateAllModelMetaData(db, Configuration.dbSchema);
		DBPool.getDBPool().releaseConnection(db);
	}

}
